package com.daffodil.storage.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import com.daffodil.storage.exception.BaseException;
import com.daffodil.storage.model.MediaMimetype;
import com.daffodil.storage.model.Page;
import com.daffodil.storage.model.StorageResult;
import com.daffodil.storage.properties.StorageProperties;
import com.daffodil.storage.properties.TencentProperties;
import com.daffodil.storage.service.AStorageService;
import com.daffodil.storage.util.StorageUtils;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.model.COSObjectSummary;
import com.qcloud.cos.model.ListObjectsRequest;
import com.qcloud.cos.model.ObjectListing;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.UploadResult;
import com.qcloud.cos.transfer.Download;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.Upload;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 腾讯云COS对象存储
 * @author yweijian
 * @date 2022年12月8日
 * @version 2.0.0
 * @description
 */
@Slf4j
@Service
@Configuration
@ConditionalOnProperty(name = StorageProperties.NAME, havingValue = StorageProperties.TENCENT)
public class StorageTencentServiceImpl extends AStorageService {

    @Autowired
    private StorageProperties storageProperties;

    @Autowired
    private COSClient cosClient;

    @Autowired
    private TransferManager transferManager;

    /** 限定列举文件的最大个数 */
    private static final Integer MAXKEYS = 1000;

    @Override
    public StorageResult upolad(StorageResult result, InputStream inputStream) {
        log.debug("腾讯云COS对象存储 > 上传对象 > fileName={}", result.getOrigin());

        TencentProperties properties = storageProperties.getTencent();
        result.setUrl(properties.getDomain() + StrUtil.SLASH + result.getKey());

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setUserMetadata(result.getMetadata());
        PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), result.getKey(), inputStream, objectMetadata);
        try {
            Upload upload = transferManager.upload(putObjectRequest);
            UploadResult uploadResult = upload.waitForUploadResult();
            if(ObjectUtil.isNotNull(uploadResult)) {
                result.setETag(uploadResult.getETag());
                log.info("腾讯云COS对象存储 > 上传对象 > eTag={}", uploadResult.getETag());
            }
        } catch (CosClientException | InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }

        log.debug("腾讯云COS对象存储 > 上传对象 > fileName={}", result.getOrigin());
        return result;
    }

    /**
     * -腾讯云COS不支持分页查询，这里实现是按业务文件目录查询[伪分页]。
     * @example { folder: dev-upload/ name: abc }
     * @remark 分页page.totalRow 总记录数只记录有效数据文件对象，不记录对象目录
     */
    @Override
    public List<StorageResult> list(String folder, String name, Page page) {
        String prefix = StorageUtils.getPrefix(folder, name);
        TencentProperties properties = storageProperties.getTencent();
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        listObjectsRequest.setBucketName(properties.getBucketName());
        listObjectsRequest.setPrefix(prefix.toString());
        listObjectsRequest.setDelimiter(StrUtil.SLASH);
        listObjectsRequest.setMaxKeys(MAXKEYS);
        ObjectListing list = null;
        try {
            list = cosClient.listObjects(listObjectsRequest);
        } catch (CosClientException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }

        List<StorageResult> results = new ArrayList<StorageResult>();
        if(list == null) {
            return results;
        }

        //目录数据
        List<String> prefixes = list.getCommonPrefixes();
        for (String key : prefixes) {
            StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                    .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(0L).mime(StrUtil.EMPTY).key(key)
                    .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
            results.add(result);
        }

        //对象数据
        List<COSObjectSummary> objectSummaries = list.getObjectSummaries();
        List<COSObjectSummary> summarys = objectSummaries.stream().filter(summary -> !summary.getKey().endsWith(StrUtil.SLASH)).collect(Collectors.toList());
        page.setTotalRow(summarys.size());
        int fromIndex = page.getFromIndex();
        int toIndex = Math.min(page.getToIndex(), summarys.size());
        if (fromIndex > toIndex) {
            return results;
        }

        for(int i = fromIndex; i < toIndex; i++) {
            COSObjectSummary summary = summarys.get(i);
            String key = summary.getKey();
            StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                    .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(summary.getSize())
                    .mime(MediaMimetype.getInstance().getMimetype(FileNameUtil.getSuffix(key))).key(key)
                    .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
            results.add(result);
        }

        return results;
    }

    @Override
    public void download(String key, HttpServletResponse response) {
        TencentProperties properties = storageProperties.getTencent();
        String fileName = FileNameUtil.getName(key);
        if(ObjectUtil.isEmpty(fileName)) {
            return;
        }
        String filePath = System.getProperty("java.io.tmpdir") + fileName;
        File file = new File(filePath);
        try {
            Download download = transferManager.download(properties.getBucketName(), key, file);
            download.waitForCompletion();

            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(fileName, StandardCharsets.UTF_8).build().toString());
            FileUtil.writeToStream(filePath, response.getOutputStream());

        } catch (CosClientException | InterruptedException | IORuntimeException | IOException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }finally {
            if(file != null && file.exists() && file.canExecute()) {
                file.delete();
            }
        }
    }

    @Override
    public void remove(String key) {
        TencentProperties properties = storageProperties.getTencent();
        try {
            cosClient.deleteObject(properties.getBucketName(), key);
        } catch (CosClientException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }
    }

}
